﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.IO;
using System.Diagnostics;
using GalaSoft.MvvmLight.Messaging;
using JmShared;
using JiveMessenger.Utilities;
using JmShared.Models;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
using JiveMessenger.Models;
using JiveMessenger.Log;

namespace JiveMessenger.Network
{
    public class NetworkConnection
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        private bool _isTempConnect = false;
        private string _clientVersion;
        private int _messageID;

        private JsonWebSocket _webSocketConnection;
        private string _currentUserID;
        private string _currentDeviceID;
        private string _deviceID;


        public event EventHandler<EventArgs> OnDisconnected;
        public event EventHandler<ChatMessageEventArgs> ChatMessageReceived;
        public event EventHandler<ChatMessageEventArgs> ChatMessageSendt;
        public event EventHandler<ChatReceiptEventArgs> ChatReceiptReceived;
        public event EventHandler<ContactEmoticonEventArgs> ContactEmoticonReceived;
        public event EventHandler<AvatarEventArgs> AvatarReceived;
        public event EventHandler<ClientListEventArgs> ClientListReceived;
        public event EventHandler<UserSettingsEventArgs> UserSettingsReceived;
        public event EventHandler<ServerInfoMessageEventArgs> ServerInfoMessageReceived;
        public event EventHandler<DeviceStatusEventArgs> DeviceStatusReceived;
        public event EventHandler<NotificationListEventArgs> NotificationListReceived;
        public event EventHandler<LoginResponseEventArgs> LoginResponseReceived;
        public event EventHandler<EmoticonRequestEventArgs> EmoticonRequestReceived;
        public event EventHandler<AvatarRequestEventArgs> AvatarRequestReceived;
        public event EventHandler<MessageQueueEventArgs> MessageQueueReceived;

        public NetworkConnection(string uniqueDeviceID, string clientVersion)
        {
            this._deviceID = uniqueDeviceID;
            this._clientVersion = clientVersion;
        }

        public Task<T> SendReceiveObject<T>(string packetheader, object obj, int timeout = 1000)
        {
                       ManualResetEvent sendReceiveEvent = new ManualResetEvent(false);
                       var returnVal = default(T);
                       _webSocketConnection.Query<T>(packetheader, obj, (o) =>
                      {
                          returnVal = o;
                          sendReceiveEvent.Set();
                      });
                       sendReceiveEvent.WaitOne(timeout);
                       return Task.FromResult(returnVal);
        }

        public async Task<DatabaseQueryResponse> VerifyPasswordResetEmail(VerifyMail msg, string server, int port)
        {
            try
            {
                DatabaseQueryResponse result = null;
                bool connectSuccess = await this.Connect(server, port, true);
                if (connectSuccess)
                {
                    result = await SendReceiveObject<DatabaseQueryResponse>(ClientCommands.VerifyResetPasswordMail.ToString(), msg, 6000);
                    if (result == null)
                        result = new DatabaseQueryResponse(false, "Timeout contacting the server");
                    _webSocketConnection.Close();
                }
                else
                {
                    result = new DatabaseQueryResponse(false, "An error occured contacting the server");
                }
                return result;
            }
            finally
            {
                if (_webSocketConnection != null)
                    _webSocketConnection.Close();
            }
        }

        public async Task<DatabaseQueryResponse> ResetPassword(PasswordReset pr, string server, int port)
        {
            try
            {
                DatabaseQueryResponse result = null;
                bool connectSuccess = await this.Connect(server, port, true);
                if (connectSuccess)
                {
                    result = await SendReceiveObject<DatabaseQueryResponse>(ClientCommands.ResetPassword.ToString(), pr, 6000);
                    if (result == null)
                        result = new DatabaseQueryResponse(false, "Timeout contacting the server");
                }
                else
                {
                    result = new DatabaseQueryResponse(false, "An error occured contacting the server");
                }
                return result;
            }
            finally
            {
                if (_webSocketConnection != null)
                    _webSocketConnection.Close();
            }
        }

        public async Task<DatabaseQueryResponse> VerifyEmail(VerifyMail msg, string server, int port)
        {
            try
            {
                DatabaseQueryResponse result = null;
                bool connectSuccess = await this.Connect(server, port, true);
                if (connectSuccess)
                {
                    result = await SendReceiveObject<DatabaseQueryResponse>(ClientCommands.VerifyRegisterAccountMail.ToString(), msg, 6000);
                    if (result == null)
                        result = new DatabaseQueryResponse(false, "Timeout contacting the server");
                    _webSocketConnection.Close();
                }
                else
                {
                    result = new DatabaseQueryResponse(false, "An error occured contacting the server");
                }
                return result;
            }
            finally
            {
                if (_webSocketConnection != null)
                    _webSocketConnection.Close();
            }
        }

        public async Task<DatabaseQueryResponse> RegisterUser(RegisterUser reg, string server, int port)
        {
            try
            {
                DatabaseQueryResponse result = null;
                bool connectSuccess = await this.Connect(server, port, true);
                if (connectSuccess)
                {
                    result = await SendReceiveObject<DatabaseQueryResponse>(ClientCommands.RegisterAccount.ToString(), reg, 6000);
                    if (result == null)
                        result = new DatabaseQueryResponse(false, "Timeout contacting the server");
                    _webSocketConnection.Close();
                }
                else
                {
                    result = new DatabaseQueryResponse(false, "An error occured contacting the server");
                }
                return result;
            }
            finally
            {
                if (_webSocketConnection != null)
                    _webSocketConnection.Close();
            }
        }
        public async Task<DatabaseQueryResponse> SetGroupPrivilegeForUser(MemberGroupPrivilege priv)
        {
            var result = await SendReceiveObject<DatabaseQueryResponse>("SetGroupPrivilege", priv, 6000);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }
        public async Task<DatabaseQueryResponse> SendQuery(ChatMessage msg)
        {
            var result = await SendReceiveObject<DatabaseQueryResponse>(ClientCommands.Message.ToString(), msg, 20000);
            if (result == null)
                result = new DatabaseQueryResponse(false, "Timeout contacting the server");
            return result;
        }

        public async Task<bool> SendEmoticonRequest(EmoticonRequest request)
        {

            if (this._webSocketConnection == null)
                return false;
            if (this._webSocketConnection.State != WebSocketState.Open)
                return false;
            try
            {
                await this._webSocketConnection.Send(ClientCommands.RequestEmoticon.ToString(), request);
                Logger.Info("Sent emoticon request to the server for {0} from the user {1}", request.KeyWord, request.Username);
            }
            catch (Exception err)
            {
                Logger.ErrorException("Error sending emoticon request: {0}", err);
                return false;
            }
            return true;
        }

        /// <summary>
        /// Sends the chatmessage to the server
        /// </summary>
        /// <param name="msg"></param>
        public async Task<bool> SendMessage(ChatMessage msg)
        {
            if (this._webSocketConnection == null)
                return false;
            if (this._webSocketConnection.State != WebSocketState.Open)
                return false;
            try
            {
                if (msg.MessageType == MessageType.GroupMessage || msg.MessageType == MessageType.PrivateMessage)
                {
                    msg.MessageID = Interlocked.Increment(ref _messageID);
                }

                await _webSocketConnection.Send(ClientCommands.Message.ToString(), msg);
                if (ChatMessageSendt != null)
                    ChatMessageSendt(this, new ChatMessageEventArgs(msg));
            }
            catch (Exception err)
            {
                Logger.ErrorException("Error sending message: {0}", err);
                return false;
            }
            return true;
        }
        public async Task<bool> SendCommand(object content, ClientCommands command)
        {
            if (this._webSocketConnection == null)
                return false;
            if (this._webSocketConnection.State != WebSocketState.Open)
                return false;
            try
            {
                if (command == ClientCommands.Message)
                    return await SendMessage(content as ChatMessage);

                await this._webSocketConnection.Send(command.ToString(), content);
            }
            catch (Exception err)
            {
                Logger.ErrorException(string.Format("Error sending {0}", command.ToString()), err);
                return false;
            }
            return true;
        }

        public void Disconnect()
        {
            _webSocketConnection.Close();
        }


        /// <summary>
        /// Disconnects from the server
        /// </summary>
        /// <returns></returns>
        public async Task<bool> Logout()
        {
            return await SendMessage(new ChatMessage(this._currentDeviceID, this._currentUserID, MessageType.Logout));
        }

        public async Task<bool> Login(string server, string username, string password, int port)
        {
            StatusBarHelper.ShowStatusBarProgress("Logging in");
            try
            {
                if (await Connect(server, port))
                {
                    UserInfo user = new UserInfo(username, _deviceID)
                    {
                        ClientStatus = ClientStatus.Available,
                    };
                    _currentUserID = user.UserID;
                    _currentDeviceID = user.ID;

                    LoginRequest info = new LoginRequest(username, StaticMethods.EncodePassword(password), _clientVersion, user);
                    info.TimezoneOffset = TimeZoneInfo.Local.BaseUtcOffset.TotalHours;
                    await SendCommand(info, ClientCommands.Login);

                    return true;
                }
            }
            finally
            {
                StatusBarHelper.HideStatusBarProgress();
            }
            return false;
        }

        /// <summary>
        /// Connects to the server
        /// </summary>
        /// <returns></returns>
        private async Task<bool> Connect(string server, int port, bool tempConnect = false)
        {
            try
            {
                this._isTempConnect = tempConnect;
                //if (_webSocketConnection != null)
                //{
                //    if (_webSocketConnection.State == WebSocketState.Open)
                //        _webSocketConnection.Close();

                //    _webSocketConnection = null;
                //}

                if (String.IsNullOrEmpty(server))
                {
                    Messenger.Default.Send(new JiveDialogMessage("Login failed", "No servers configured"));
                    if (!tempConnect)
                    {
                        if (OnDisconnected != null)
                            OnDisconnected(this, null);
                    }
                    return false;
                }

                string protocol = "ws";
#if WINDOWS_PHONE_APP && DEBUG
                protocol = "ws";
#endif
                _webSocketConnection = new JsonWebSocket(new Uri(string.Format("{0}://{1}:{2}", protocol, server, port)));
                
                //_webSocketConnection.EnableAutoSendPing = true;
                //_webSocketConnection.AutoSendPingInterval = 3;
                _webSocketConnection.On<ServerInfoMessage>(ServerCommands.ServerInfoMessage.ToString(), ProcessServerInfoMessage);
                _webSocketConnection.On<ChatMessage>(ClientCommands.Message.ToString(), ProcessMessage);
                _webSocketConnection.On<ChatReceipt>(ServerCommands.ChatReceipt.ToString(), ProcessReceipt);
                _webSocketConnection.On<ClientList>(ServerCommands.ClientList.ToString(), ProcessClientList);
                _webSocketConnection.On<DeviceStatus>(ClientCommands.DeviceStatusUpdate.ToString(), ProcessDeviceStatus);
                _webSocketConnection.On<ContactEmoticonTransfer>(ClientCommands.CustomEmoticon.ToString(), ProcessEmoticon);
                _webSocketConnection.On<NotificationList>(ClientCommands.NotificationsUpdate.ToString(), ProcessNotifications);
                _webSocketConnection.On<WinRTLoginResponse>(ServerCommands.LoginResponse.ToString(), ProcessLoginResponse);
                _webSocketConnection.On<AvatarTransfer>(ServerCommands.Avatar.ToString(), ProcessAvatar);
                _webSocketConnection.On<AvatarRequest>(ServerCommands.RequestAvatar.ToString(), ProcessAvatarRequest);
                _webSocketConnection.On<EmoticonRequest>(ServerCommands.RequestEmoticon.ToString(), ProcessEmoticonRequest);
                _webSocketConnection.On<MessageQueue>(ServerCommands.MessageQueue.ToString(), ProcessMessageQueue);
                _webSocketConnection.Error += websocket_Error;
                _webSocketConnection.Closed += websocket_Closed;

                await _webSocketConnection.Open();
                return true;
            }
            catch (Exception ex)
            {
                //throw;
                Logger.ErrorException(ex.Message, ex);
#if DEBUG
                Messenger.Default.Send(new JiveDialogMessage("Login failed", ex.ToString()));

                //#else
                //                Messenger.Default.Send(new JiveDialogMessage("Login failed", "Could not connect to the server"));

#endif
                return false;
            }
        }



        private void websocket_Closed(object sender, WebSocketClosedEventArgs e)
        {
            if (_webSocketConnection != null)
            {
                _webSocketConnection.Error -= new EventHandler<ErrorEventArgs>(websocket_Error);
            }
            _currentUserID = string.Empty;
            if (OnDisconnected != null && !_isTempConnect)
                OnDisconnected(this, null);
            _isTempConnect = false;
        }


        private void websocket_Error(object sender, ErrorEventArgs e)
        {
            Logger.ErrorException("Error in websocket connection", e.Exception);
        }
        private void ProcessMessageQueue(MessageQueue queue)
        {
            if (MessageQueueReceived != null)
                MessageQueueReceived(this, new MessageQueueEventArgs(queue));
            Logger.Debug("Received message of type MessageQueue");
        }
        private void ProcessEmoticonRequest(JsonWebSocket conn, EmoticonRequest request)
        {
            if (EmoticonRequestReceived != null)
                EmoticonRequestReceived(this, new EmoticonRequestEventArgs(request));
            Logger.Debug("Received message of type EmoticonRequest");
        }

        private void ProcessAvatarRequest(JsonWebSocket conn, AvatarRequest request)
        {
            if (AvatarRequestReceived != null)
                AvatarRequestReceived(this, new AvatarRequestEventArgs(request));
            Logger.Debug("Received message of type AvatarRequest");
        }
        private void ProcessLoginResponse(JsonWebSocket conn, WinRTLoginResponse response)
        {
            if (LoginResponseReceived != null)
                LoginResponseReceived(this, new LoginResponseEventArgs(response));
            Logger.Debug("Received message of type LoginResponse");
        }
        private void ProcessNotifications(JsonWebSocket conn, NotificationList notificationList)
        {
            if (notificationList != null)
                NotificationListReceived(this, new NotificationListEventArgs(notificationList));
            Logger.Debug("Received message of type NotificationList");
        }
        private void ProcessDeviceStatus(JsonWebSocket conn, DeviceStatus deviceStatus)
        {
            if (DeviceStatusReceived != null)
                DeviceStatusReceived(this, new DeviceStatusEventArgs(deviceStatus));
            Logger.Debug("Received message of type DeviceStatus");
        }
        private void ProcessServerInfoMessage(JsonWebSocket conn, ServerInfoMessage serverInfoMsg)
        {
            if (ServerInfoMessageReceived != null)
                ServerInfoMessageReceived(this, new ServerInfoMessageEventArgs(serverInfoMsg));
            Logger.Debug("Received message of type ServerInfoMessage");
        }

        private void ProcessUserSettings(JsonWebSocket conn, WpUserSettings settings)
        {
            if (UserSettingsReceived != null)
                UserSettingsReceived(this, new UserSettingsEventArgs(settings));
            Logger.Debug("Received message of type WpUserSettings");
        }

        private void ProcessClientList(JsonWebSocket conn, ClientList clients)
        {
            if (ClientListReceived != null)
                ClientListReceived(this, new ClientListEventArgs(clients));
            Logger.Debug("Received message of type ClientList");
        }
        private void ProcessAvatar(JsonWebSocket conn, AvatarTransfer avatar)
        {
            if (AvatarReceived != null)
                AvatarReceived(this, new AvatarEventArgs(avatar));
            Logger.Debug("Received message of type Avatar");
        }
        private void ProcessEmoticon(JsonWebSocket conn, ContactEmoticonTransfer emoticon)
        {
            if (ContactEmoticonReceived != null)
                ContactEmoticonReceived(this, new ContactEmoticonEventArgs(emoticon));
            Logger.Debug("Received message of type ContactEmoticon");
        }

        private void ProcessReceipt(JsonWebSocket conn, ChatReceipt receipt)
        {
            if (ChatReceiptReceived != null)
                ChatReceiptReceived(this, new ChatReceiptEventArgs(receipt));
            Logger.Debug("Received message of type ChatReceipt");
        }

        private void ProcessMessage(JsonWebSocket conn, ChatMessage msg)
        {
            if (ChatMessageReceived != null)
                ChatMessageReceived(this, new ChatMessageEventArgs(msg));
            Logger.Debug("Received message of type ChatMessage");
        }
    }
    public class MessageQueueEventArgs : EventArgs
    {
        public MessageQueue MessageQueue { get; private set; }
        public MessageQueueEventArgs(MessageQueue queue)
        {
            MessageQueue = queue;
        }
    }
    public class AvatarRequestEventArgs : EventArgs
    {
        public AvatarRequest AvatarRequest { get; private set; }
        public AvatarRequestEventArgs(AvatarRequest request)
        {
            this.AvatarRequest = request;
        }
    }

    public class EmoticonRequestEventArgs : EventArgs
    {
        public EmoticonRequest EmoticonRequest { get; private set; }
        public EmoticonRequestEventArgs(EmoticonRequest request)
        {
            this.EmoticonRequest = request;
        }
    }
    public class LoginResponseEventArgs : EventArgs
    {
        public WinRTLoginResponse LoginResponse { get; private set; }
        public LoginResponseEventArgs(WinRTLoginResponse response)
        {
            this.LoginResponse = response;
        }
    }
    public class NotificationListEventArgs : EventArgs
    {
        public NotificationList NotificationList { get; private set; }
        public NotificationListEventArgs(NotificationList list)
        {
            this.NotificationList = list;
        }
    }
    public class ChatMessageEventArgs : EventArgs
    {
        public ChatMessage Message { get; private set; }
        public ChatMessageEventArgs(ChatMessage msg)
        {
            this.Message = msg;
        }
    }
    public class DeviceStatusEventArgs : EventArgs
    {
        public DeviceStatus Status { get; private set; }
        public DeviceStatusEventArgs(DeviceStatus status)
        {
            this.Status = status;
        }
    }
    public class ServerInfoMessageEventArgs : EventArgs
    {
        public ServerInfoMessage Message { get; private set; }
        public ServerInfoMessageEventArgs(ServerInfoMessage msg)
        {
            this.Message = msg;
        }
    }
    public class UserSettingsEventArgs : EventArgs
    {
        public WpUserSettings Settings { get; private set; }
        public UserSettingsEventArgs(WpUserSettings settings)
        {
            this.Settings = settings;
        }
    }
    public class ClientListEventArgs : EventArgs
    {
        public ClientList ClientList { get; private set; }
        public ClientListEventArgs(ClientList clients)
        {
            this.ClientList = clients;
        }
    }
    public class ContactEmoticonEventArgs : EventArgs
    {
        public ContactEmoticonTransfer Emoticon { get; private set; }
        public ContactEmoticonEventArgs(ContactEmoticonTransfer emoticon)
        {
            this.Emoticon = emoticon;
        }
    }
    public class AvatarEventArgs : EventArgs
    {
        public AvatarTransfer Avatar { get; private set; }
        public AvatarEventArgs(AvatarTransfer avatar)
        {
            this.Avatar = avatar;
        }
    }
    public class ChatReceiptEventArgs : EventArgs
    {
        public ChatReceipt Receipt { get; private set; }
        public ChatReceiptEventArgs(ChatReceipt receipt)
        {
            this.Receipt = receipt; ;
        }
    }


}
